package com.aigou.admin.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
 * JDBC工具类
 */
public class JDBCUtils {

    //数据源
    /**
     * 静态数据源实例
     * <p>
     * 使用静态变量存储数据源实例可以在整个应用程序中共享此数据源
     * 避免了重复创建数据源实例的开销，有助于提高性能和资源利用率
     */
    private static DataSource dataSource = null;

    /**
     * 初始化连接池数据源
     * <p>
     * 在静态代码块中加载配置文件并初始化数据源实例。
     * 通过读取 `druid.properties` 文件中的配置信息，使用 `DruidDataSourceFactory` 创建数据源实例。
     * 如果在加载配置文件或创建数据源过程中发生异常，将捕获异常并打印堆栈跟踪信息。
     */
    static {
        Properties properties = new Properties();
        try {
            //JDBCUtils.class.getClassLoader(): 获取当前类的类加载器
            //getResourceAsStream("druid.properties"):
            // 从类路径中加载名为 druid.properties 的资源文件，并返回一个 InputStream 对象
            //Properties 类是 Hashtable 的子类，专门用于处理属性文件（通常是 .properties 文件）。
            //属性文件通常包含键值对，每行一个键值对，格式为 key=value。
            //load(InputStream inStream) 方法:这个方法从指定的输入流中读取属性列表。
            //输入流通常是从文件、网络或其他资源中获取的。
            // 方法会解析输入流中的每一行，将键值对加载到 Properties 对象中。
            properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));

            //这是 DruidDataSourceFactory 类中的一个静态方法，用于创建数据源实例。
            //参数 properties 是一个 Properties 对象，包含数据源的配置信息，
            // 如数据库驱动类名、URL、用户名、密码等。
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        }
        //catch 关键字用于定义一个异常处理块。
        //Exception 是所有异常的基类，捕获 Exception 意味着可以捕获任何类型的异常。
        //e 是捕获到的异常对象的引用，可以通过这个引用来访问异常的详细信息。
        catch (Exception e) {
            //printStackTrace() 是 Throwable 类（Exception 的父类）的一个方法，
            // 用于将异常的堆栈跟踪信息打印到标准错误流（通常是控制台）。
            //堆栈跟踪信息包括异常的类型、异常消息以及异常发生时的调用栈，可以帮助开发者定位和调试问题。
            e.printStackTrace();
        }
    }


    /**
     * 执行增删改操作
     *
     * @param sql SQL语句，可以是INSERT、UPDATE或DELETE语句
     * @param params 可变参数，用于传递SQL语句中的占位符参数值
     * @return 影响的行数，表示成功执行的记录数
     */
    public static int executeUpdate(String sql, Object... params) {
        // 创建数据库连接，并使用try-with-resources语句确保连接在操作完成后自动关闭
        // dataSource.getConnection()：从数据源获取数据库连接
        try (Connection connection = dataSource.getConnection()) {
            // 创建PreparedStatement对象，用于执行预编译的SQL语句使用
            // connection.prepareStatement(sql) 方法创建 PreparedStatement 对象，
            // 用于执行预编译的SQL语句。
            PreparedStatement preparedStatement = connection.prepareStatement(sql);

            // for (int i = 0; i < params.length; i++)
            // { preparedStatement.setObject(i + 1, params[i]); }
            // 遍历 params 数组，将每个参数值设置到SQL语句中的相应占位符位置。
            // INSERT INTO users (username, password) VALUES ('john', 'secret');
            // INSERT INTO users (username, password) VALUES (?, ?);
            //preparedStatement.setObject(i + 1, params[i])：
            // 将第 i+1 个占位符设置为 params[i] 的值。

            // 为什么要使用占位符，为什么可以避免SQL注入攻击？
            // String username = "john' OR '1'='1";
            //String sql = "SELECT * FROM users WHERE username = '" + username + "'";
            //Statement statement = connection.createStatement();
            //ResultSet rs = statement.executeQuery(sql);
            //在这个例子中，如果 username 包含恶意的SQL代码（如 'john' OR '1'='1），SQL语句将变成：

            // String username = "john' OR '1'='1";
            //String sql = "SELECT * FROM users WHERE username = ?";
            //
            //try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            //    preparedStatement.setString(1, username);
            //    ResultSet rs = preparedStatement.executeQuery();
            //}在这个例子中，即使 username 包含恶意的SQL代码，
            // preparedStatement.setString 方法会将 username 作为普通字符串处理，
            // 最终生成的SQL语句将是：SELECT * FROM users WHERE username = 'john'' OR ''1''=''1'

            for (int i = 0; i < params.length; i++) {
                preparedStatement.setObject(i + 1, params[i]);
            }

            // 执行SQL语句并获取影响的行数
            //executeUpdate() 方法用于执行SQL的 INSERT、UPDATE 或 DELETE 语句。
            //这个方法返回一个整数值，表示受影响的行数。例如，如果你执行了一个 UPDATE 语句，
            // 返回值就是被更新的行数。
            int rows = preparedStatement.executeUpdate();

            // 返回影响的行数
            return rows;
        }
        //catch 关键字用于定义一个异常处理块。
        //Exception 是所有异常的基类，捕获 Exception 意味着可以捕获任何类型的异常。
        //e 是捕获到的异常对象的引用，可以通过这个引用来访问异常的详细信息。
        catch (SQLException ex) {
            //printStackTrace() 是 Throwable 类（Exception 的父类）的一个方法，
            // 用于将异常的堆栈跟踪信息打印到标准错误流（通常是控制台）。
            //堆栈跟踪信息包括异常的类型、异常消息以及异常发生时的调用栈，可以帮助开发者定位和调试问题。
            ex.printStackTrace();

            // 将捕获的SQLException包装成RuntimeException并向上抛出
            //public void someMethod() {
            //    try {
            //        // 执行数据库操作
            //        Connection connection = dataSource.getConnection();
            //        PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM users");
            //        ResultSet rs = preparedStatement.executeQuery();
            //        // 处理结果集
            //    } catch (SQLException ex) {
            //        ex.printStackTrace();
            //        throw new RuntimeException(ex);
            //    }
            //}在这个示例中，如果 someMethod 在执行数据库操作时捕获到 SQLException，
            // 它会打印异常堆栈跟踪信息，然后将 SQLException 封装成 RuntimeException 并抛出。
            // 这样，调用 someMethod 的代码可以处理 RuntimeException，
            // 而不需要显式地处理 SQLException。

            //什么是显式异常，在Java中，SQLException 是一个受检异常（checked exception），
            // 这意味着在编译时，如果方法中可能会抛出 SQLException，
            // 那么你需要在方法签名中声明 throws SQLException，或者在方法内部捕获并处理这个异常。
            //然而，在某些情况下，你可能不希望在每个方法中都显式地处理 SQLException，
            // 而是希望将异常传递给调用者，让调用者来处理。
            // 这时，你可以将 SQLException 转换为一个未受检异常（unchecked exception），
            // 例如 RuntimeException。
            throw new RuntimeException(ex);
        }
    }


    /**
     * 执行查询操作
     * @param clazz 要查询的实体类类型 如： Student.class
     * @param sql SQL语句
     * @param params 占位符参数
     * @param <T> 泛型类型
     * @return 泛型集合
     */
    public static <T> List<T> executeQuery(Class clazz,String sql,Object... params){
        try(Connection connection = dataSource.getConnection()){
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            for(int i = 0;i < params.length;i++){
                preparedStatement.setObject(i + 1,params[i]);
            }
            //执行查询获得ResultSet
            ResultSet rs = preparedStatement.executeQuery();
            //创建List集合
            List<T> list = new ArrayList<>();
            //遍历结果集
            while(rs.next()){
                //使用反射机制创建对象 new Student();
                Object object = clazz.newInstance();
                //给对象的所有属性赋值
                //使用反射获得类中所有属性 Student id name age gender address
                Field[] fields = clazz.getDeclaredFields();
                //遍历所有属性
                for(Field field : fields){
                    //通过属性名，将结果集中的列的值读出来
                    Object value = rs.getObject(field.getName());
                    //设置属性可以访问
                    field.setAccessible(true);
                    //给属性赋值 对象.属性 = 值
                    field.set(object,value);
                }
                //添加对象到List集合
                list.add((T)object);
            }
            return list;
        }catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
    }
}
